#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
File devtools.py: function decorator that performs range-test
validation for passed arguments. Arguments are specified by
keyword to the decorator. In the actual call, arguments may
be passed by position or keyword, and defaults may be omitted.
See devtools_test.py for example use cases.
"""

def rangetest(trace=False, **argchecks):
    def onDecorator(func):
        if not trace:
            return func
        else:
            allargs = func.__code__.co_varnames[:func.__code__.co_argcount]
            def onCall(*args, **kwargs):
                positionals = list(allargs)
                positionals = positionals[:len(args)]
                for (argname, (low, high)) in argchecks.items():
                    if argname in kwargs:
                        if kwargs[argname] < low or kwargs[argname] > high:
                            raise TypeError('TypeError: {0} argument {1} not in {2}~{3}'.format(
                                func.__name__, argname, low, high))
                    elif argname in positionals:
                        position = positionals.index(argname)
                        if args[position] < low or args[position] > high:
                            raise TypeError('TypeError: {0} argument {1} not in {2}~{3}'.format(
                                func.__name__, argname, low, high))
                    else:
                        print('Argument "{0}" defaulted'.format(argname))
                return func(*args, **kwargs)
            return onCall
    return onDecorator

@rangetest(trace=True, age=(0, 120))
def personinfo(name, age):
    print('{0} is {1} years old'.format(name, age))

@rangetest(trace=True, M=(1, 12), D=(1, 31), Y=(0, 2018))
def birthday(M, D, Y):
    print('birthday = {0}/{1}/{2}'.format(M, D, Y))

class Person:
    def __init__(self, name, job, pay):
        self.name = name
        self.job = job
        self.pay = pay

    @rangetest(trace=True, percent=(0.0, 1.0))
    def giveRaise(self, percent=0.5):
        self.pay = int(self.pay * (1 + percent))


if __name__ == '__main__':
    personinfo(name='Bob Smith', age=45)
    try:
        personinfo('Bob Smith', 200)
    except TypeError as e:
        print(e)
    print('')

    birthday(5, 31, 1963)
    try:
        birthday(M=5, D=32, Y=1963)
    except TypeError as e:
        print(e)
    print('')

    sue = Person('Sue Jones', 'dev', 100000)
    sue.giveRaise(.10)
    print(sue.pay)
    sue.giveRaise()
    try:
        sue.giveRaise(percent=1.10)
        print(sue.pay)
    except TypeError as e:
        print(e)
